Programm

Womit beschäftigen wir uns heute?

  • Datentypen in R
  • Öffnen und Speichern von Dateien
  • Umgang mit Tabellen
  • Beschreibung von Daten
  • Visualisierung von Daten
  • statistische Modellierung: (generalisierte) lineare Modelle
  • Keywords und Kollokate
  • Fragen!

Zur Erinnerung: Die Hilfe zu einer Funktion lässt sich aufrufen, indem man dem Funktionsnamen ein Fragezeichen voranstellt:

?help

Und mit ?? lässt sich nach vagen Begriffen suchen:

??randomforest

Pakete einbinden

Benötigte Pakete lassen sich zwar grundsätzlich jederzeit einbinden, es bietet sich aber an, dies zu Beginn eines Skripts zu tun, um den Überblick zu behalten.

library(tidyverse)
library(readxl) # für Excel-Tabellen
library(psych)
library(skimr)
library(corpora)
library(ggcorrplot)

Datentypen

Im Vorbereitungsskript ging es bereits um Zahlen, logische Werte (TRUE/FALSE), Zeichenketten/Strings (R-Bezeichnung: character) und Vektoren.

Faktoren

Faktoren sind speziell für kategoriale Daten gedacht. Ein Faktor hat eine feste Menge von Levels/möglichen Ausprägungen:

geschlecht <- c(rep("m", 10), rep("w", 20)) |>
  sample() |> # durchmischen, damit es nach echten Daten aussieht
  factor() 
geschlecht
##  [1] w w m m w w w w w m w w w w w w m w m m w m w m w w w w m m
## Levels: m w

Um die Namen der Ausprägungen/Levels zu verändern, können wir levels() verwenden:

levels(geschlecht) <- c("männlich", "weiblich")
geschlecht
##  [1] weiblich weiblich männlich männlich weiblich weiblich weiblich weiblich
##  [9] weiblich männlich weiblich weiblich weiblich weiblich weiblich weiblich
## [17] männlich weiblich männlich männlich weiblich männlich weiblich männlich
## [25] weiblich weiblich weiblich weiblich männlich männlich
## Levels: männlich weiblich

Wenn man einen Faktor verwendet, um unterschiedliche Gruppen zu definieren, ist es sinnvoll, die Kontrollgruppe (oder die Gruppe, die als Vergleichsbasis dient), als erste aufzuführen. (Weil viele Funktionen die erste Gruppe als Referenzgruppe nehmen.)

Am einfachsten geht das vielleicht mit der Funktion relevel():

f <- factor(c("a", "a", "c", "b", "b", "b", "d"))
relevel(f, "b")
## [1] a a c b b b d
## Levels: b a c d

Mit der Funktion fct_relevel() (aus tidyverse/forcats) kann man die Levels beliebig verschieben:

fct_relevel(f, "b") # wie relevel()
## [1] a a c b b b d
## Levels: b a c d
fct_relevel(f, "b", after = 2)
## [1] a a c b b b d
## Levels: a c b d
fct_relevel(f, "a", after = Inf) # ans Ende
## [1] a a c b b b d
## Levels: b c d a

(In dem Paket befinden sich noch einige weitere sehr praktische Funktionen zum Umgang mit Faktoren, z.B. fct_lump() oder fct_expand().)

Für ordinalskalierte Daten können wir statt factor() die Funktion ordered() verwenden, um R mitzuteilen, dass die Daten eine Rangordnung haben:

bewertung <- c(rep("stimme voll zu", 3),
               rep("stimme eher zu", 6),
               rep("weder noch", 8),
               rep("stimme eher nicht zu", 4),
               rep("stimme gar nicht zu", 1)) |>
  sample() # Unordnung
ordered(bewertung)
##  [1] weder noch           stimme eher nicht zu stimme gar nicht zu 
##  [4] stimme eher zu       weder noch           stimme eher zu      
##  [7] weder noch           stimme eher nicht zu stimme eher zu      
## [10] stimme eher nicht zu stimme voll zu       stimme eher zu      
## [13] stimme eher zu       weder noch           stimme eher zu      
## [16] stimme voll zu       stimme eher nicht zu weder noch          
## [19] weder noch           stimme voll zu       weder noch          
## [22] weder noch          
## 5 Levels: stimme eher nicht zu < stimme eher zu < ... < weder noch

Ohne weitere Argumente sortiert die Funktionen die Ausprägungen allerdings bloß alphabetisch – also müssen wir die Reihenfolge vorgeben:

bewertung <- ordered(bewertung, levels = c("stimme gar nicht zu", 
                                           "stimme eher nicht zu", 
                                           "weder noch", 
                                           "stimme eher zu", 
                                           "stimme voll zu"))
bewertung
##  [1] weder noch           stimme eher nicht zu stimme gar nicht zu 
##  [4] stimme eher zu       weder noch           stimme eher zu      
##  [7] weder noch           stimme eher nicht zu stimme eher zu      
## [10] stimme eher nicht zu stimme voll zu       stimme eher zu      
## [13] stimme eher zu       weder noch           stimme eher zu      
## [16] stimme voll zu       stimme eher nicht zu weder noch          
## [19] weder noch           stimme voll zu       weder noch          
## [22] weder noch          
## 5 Levels: stimme gar nicht zu < stimme eher nicht zu < ... < stimme voll zu

Damit lässt sich der geordnete Faktor nun übrigens auch nach Größe sortieren (statt nur alphabetisch):

sort(bewertung)
##  [1] stimme gar nicht zu  stimme eher nicht zu stimme eher nicht zu
##  [4] stimme eher nicht zu stimme eher nicht zu weder noch          
##  [7] weder noch           weder noch           weder noch          
## [10] weder noch           weder noch           weder noch          
## [13] weder noch           stimme eher zu       stimme eher zu      
## [16] stimme eher zu       stimme eher zu       stimme eher zu      
## [19] stimme eher zu       stimme voll zu       stimme voll zu      
## [22] stimme voll zu      
## 5 Levels: stimme gar nicht zu < stimme eher nicht zu < ... < stimme voll zu

Umwandlungsfunktionen

Wir können Daten auch in einen anderen Datentyp umwandeln:

as.integer(41.728)
## [1] 41
as.character(.345)
## [1] "0.345"
as.double("5.23")
## [1] 5.23
as.integer(c(TRUE, FALSE, TRUE, TRUE, FALSE))
## [1] 1 0 1 1 0

Umgang mit Datensätzen

R kann mit Daten in verschiedensten Formaten umgehen; wir wollen uns hier aber auf typische Tabellenformate beschränken.

Tabellenaufbau

Tabellen sind im Grunde genommen Listen von Vektoren gleicher Länge (wenn dabei alle Daten numerisch sind, können wir anstelle einer Tabelle auch eine Matrix verwenden).

Tabellen lesen wir normalerweise aus Dateien ein. R bevorzugt das CSV-Format (comma-separated values/character-separated values), mit geeigneten Paketen lassen sich aber auch die Dateiformate von Excel (z.B. .xslx), SPSS usw. einlesen.

Zu sehr ins Detail können wir leider nicht gehen. Wer das tun möchte, kann sich aber bspw. diesen DataCamp-Kurs ansehen: https://learn.datacamp.com/courses/importing-data-in-r-part-1

R und einige Pakete liefern netterweise gleich einige Datensätze zu Testzwecken mit, sodass wir uns direkt ansehen können, wie so eine Tabelle aussieht:

mtcars # im "data.frame"-Format
mpg # im "tibble"-Format (tidyverse/tibble)

An beiden Beispielen sehen wir, dass Tabellen aus Zeilen (rows) und Spalten (columns) bestehen. Die erste Zeile ist üblicherweise die Kopfzeile (header), in der die Namen der Spalten stehen. Bei einem tibble wird direkt darunter für jede Spalte ausgegeben, welchen Datentyp sie hat:

  • chr für character (Zeichenketten/Strings)
  • fct für factor (Variablen, die nur bestimmte Ausprägungen annehmen können)
  • int für integer (ganze Zahlen)
  • dbl für double (Fließkommazahlen, also mit Nachkommastellen)
  • lgl für logical (TRUE oder FALSE)

Volle Liste: https://tibble.tidyverse.org/articles/types.html

Ein “data.frame”-Objekt lässt sich in ein “tibble” umwandeln:

as_tibble(mtcars)

Nun werden nicht mehr alle Zeilen auf der Konsole ausgegeben. Das ist normalerweise wünschenswert. Man kann aber mit der Funktion print Zeilenanzahl und maximale Tabellenbreite (in Zeichen) angeben:

as_tibble(mtcars) |>
  print(n = 20, width = 40) # "width = Inf" für alle Spalten 
## # A tibble: 32 × 11
##      mpg   cyl  disp    hp  drat    wt
##    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
##  1  21       6 160     110  3.9   2.62
##  2  21       6 160     110  3.9   2.88
##  3  22.8     4 108      93  3.85  2.32
##  4  21.4     6 258     110  3.08  3.22
##  5  18.7     8 360     175  3.15  3.44
##  6  18.1     6 225     105  2.76  3.46
##  7  14.3     8 360     245  3.21  3.57
##  8  24.4     4 147.     62  3.69  3.19
##  9  22.8     4 141.     95  3.92  3.15
## 10  19.2     6 168.    123  3.92  3.44
## 11  17.8     6 168.    123  3.92  3.44
## 12  16.4     8 276.    180  3.07  4.07
## 13  17.3     8 276.    180  3.07  3.73
## 14  15.2     8 276.    180  3.07  3.78
## 15  10.4     8 472     205  2.93  5.25
## 16  10.4     8 460     215  3     5.42
## 17  14.7     8 440     230  3.23  5.34
## 18  32.4     4  78.7    66  4.08  2.2 
## 19  30.4     4  75.7    52  4.93  1.62
## 20  33.9     4  71.1    65  4.22  1.84
## # … with 12 more rows, and 5 more
## #   variables: qsec <dbl>, vs <dbl>,
## #   am <dbl>, gear <dbl>, carb <dbl>

Tabellen anlegen

Um eine Tabelle als tibble zu erzeugen:

tibble(
  Rang = 1:6,
  Frequenz = c(555503423, 525757950, 258037531, 254258892, 244546490, 
               135861000),
  Freq_pMT = Frequenz / 11660894000 * 1e6,
  Token = c(",", ".", "und", "die", "der", "in")
)

(Dies sind die ersten sechs Zeilen der Frequenzliste des DECOW14, einer riesigen deutschen Sammlung von Texten aus dem Web.)

Schön daran: Einmal definierte Spalten lassen sich direkt für Berechnungen für weitere Spalten verwenden (siehe Frequenz).

Um auf eine Tabelle als Objekt zugreifen zu können, gehen wir genauso vor wie auch bei Einzelwerten und Vektoren:

decow <- tibble(
  Rang = 1:6,
  Frequenz = c(555503423, 525757950, 258037531, 254258892, 244546490, 
               135861000),
  Freq_pMT = Frequenz / 11660894000 * 1e6,
  Token = c(",", ".", "und", "die", "der", "in")
)

Die Tabelle sollte jetzt als Objekt unter “Environment” auftauchen. Ein Klick darauf zeigt uns die ganze Tabelle (das gleiche bewirkt auch die Funktion View()).

Tabellendateien öffnen

Wer schon Erfahrung mit R hat, kennt wahrscheinlich schon read.table(), read.csv(), read.csv2() usw. Alternativ kann man Dateien aber auch direkt im “tibble”-Format öffnen. (Und für sehr große Dateien bietet sich das Paket “data.table” mit der Funktion fread() an, die den Einleseprozess enorm beschleunigen kann.)

Welche Funktion man am besten verwendet, hängt vom Dateiformat und der Formatierung ab (also z.B. davon, mit welchen Zeichen Werte voneinander getrennt sind, was als Dezimaltrennzeichen verwendet wird usw.).

?read_delim # tidyverse/readr
?read.table # Basis-R

Für CSV-Dateien im europäischen Format (Semikolon als Trennzeichen, Komma als Dezimaltrennzeichen) verwenden wir read_csv2():

complexity <- read_csv2("../data/sentence_complexity.csv")
## ℹ Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
## Rows: 1000 Columns: 11
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ";"
## chr  (1): sentence
## dbl (10): id, mean_word_length, mean_syllables, max_syllables, lexical_densi...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
complexity

Mit teilweise spezifizierten Datentypen:

complexity <- read_csv2("../data/sentence_complexity.csv",
                        col_types = cols(id = "i",
                                         max_syllables = "i",
                                         tokens = "i",
                                         max_dependency_distance = "i",
                                         fin_verbs = "i"))
## ℹ Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
complexity

Als Argument übergibt man der Funktion hier einen Dateipfad. Dieser kann entweder absolut angegeben werden (unter Windows also beginnend mit dem Laufwerksbuchstaben) oder relativ zum aktuellen Arbeitsverzeichnis oder – in .Rmd-Dateien – relativ zum Speicherort des aktuellen Skripts (wie hier geschehen).

Alternativ kann man die Funktion file.choose() verwenden, um über ein Dialogfenster eine Datei auszuwählen:

read_csv2(file.choose())

Neben read_csv2() gibt es read_csv() für das amerikanische Format (Komma als Trennzeichen, Punkt als Dezimaltrennzeichen), read_tsv() für Dateien, in denen Tabulatoren als Trennzeichen verwendet werden, und read_delim() als übergeordnete Funktion, bei der man selbst angeben kann, welche Zeichen als Trennzeichen usw. verwendet werden.

In RStudio kann man über File -> Import Dataset verschiedene Möglichkeiten aufrufen, Tabellendateien zu öffnen (mit Vorschau und verschiedenen Optionen):

  • From Text (base)…: basiert auf den Grundfunktionen, also read.table() usw. (“data.frame”-Format)
  • From Text (readr)…: “tibble”-Format
  • From Excel…: Excel-Dateien (“tibble”-Format)
  • From SPSS…: SPSS-Dateien (“tibble”-Format)
  • From SAS…: SAS-Dateien (“tibble”-Format)
  • From Stata…: Stata-Dateien (“tibble”-Format)

Klickt man nach Auswahl passender Optionen dann auf “Import”, kann man sich den verwendeten R-Befehl auch aus der Konsole in sein eigenes Skript kopieren.

Ein Beispiel zum Öffnen einer Excel-Datei:

complexity <- read_excel("../data/sentence_complexity.xlsx", sheet = 1)
complexity

Wer eigene Daten dabei hat, sollte spätestens an dieser Stelle einmal ausprobieren, sie zu öffnen. Erfahrungsgemäß tauchen dabei immer irgendwelche Probleme auf …

Tabellen speichern

Um Tabellen zu speichern, gibt es analog zu den diversen read_-Funktionen passende write_-Funktionen, denen man zuerst den Namen des Objekts, dann den gewünschten Speicherpfad und ggf. weitere Parameter übergibt:

write_csv2(complexity, file.choose())
write_csv2(complexity, "../data/sentence_complexity.csv")

Auf Tabellenteile zugreifen

Um auf einzelne Spalten (meist einzelne statistische Variablen) zuzugreifen, geben wir den Namen der Tabelle ein, gefolgt von einem Dollarzeichen und dem Namen der Spalte. Wir erhalten einen Vektor:

decow$Token
## [1] ","   "."   "und" "die" "der" "in"
decow$Frequenz
## [1] 555503423 525757950 258037531 254258892 244546490 135861000

Um auf Teile der Tabelle zuzugreifen, kann man eckige Klammern verwenden. Das funktioniert genauso wie mit Vektoren, nur dass wir diesmal zwei Positionen angeben müssen: Zeile und Spalte.

decow[2, 3] # zweite Zeile, dritte Spalte
decow[3, 2:4] # dritte Zeile, Spalten 2 bis 4
decow[3,] # dritte Zeile, alle Spalten (Komma nicht vergessen!)
decow[, 3] # dritte Spalte (geht auch ohne Komma, ist dann aber verwirrender ...)
decow[c(1, 3),] # 1 und dritte Zeile, alle Spalten

Weil man die richtige Reihenfolge erfahrungsgemäß gerne einmal vergisst, hilft vielleicht ein alberner Merkspruch:

Erst die Zeile, dann die Spalt’,
Bis es auch der letzte schnallt.

(Bessere Vorschläge sind jederzeit willkommen.)

Zur Auswahl bestimmter Spalten gibt es auch noch select():

decow |>
  select(Freq_pMT, Token)
decow |>
  select(Frequenz:Token)

Umbenennen kann man die Variablen dabei auch:

decow |>
  select(Frequenz_pMT = Freq_pMT, Token)

(Achtung, das ist erst einmal nur die Ausgabe der Funktion, das Objekt decow bleibt unverändert – sofern wir ihm die Ausgabe nicht zuweisen.)

Wenn es nur ums Umbenennen geht, man aber eigentlich die übrigen Spalten beibehalten möchte, ist rename() praktischer:

decow |>
  rename(Frequenz_pMT = Freq_pMT)

select() ist auch praktisch, wenn man bloß die Reihenfolge der Spalten verändern möchte:

decow |>
  select(Token, everything()) # everything(): kleine Hilfsfunktion

Tabellen filtern

Oft will man Teile einer Tabelle nicht anhand ihrer Position auswählen, sondern anhand bestimmter Kriterien, die die enthaltenen Daten erfüllen müssen. Dafür gibt es die Funktion filter().

Welche Sätze im eben eingelesenen Datensatz wurden z.B. als besonders komplex bewertet?

complexity |>
  filter(mean_complexity > 6)

Wenn mehrere Bedingungen gleichzeitig erfüllt sein sollen, kann man sie mit Kommata abtrennen:

complexity |>
  filter(tokens >= 30, mean_word_length >= 5)

Ein logisches UND funktioniert aber ebenso:

complexity |>
  filter(tokens >= 30 & mean_word_length >= 5)

Logische Operatoren, die hier verwendet werden können, kamen schon in der Datei zur Vorbereitung vor. Zur Übung:

  • alle Zeilen auswählen, in denen denen genau vier finite Verben (fin_verbs) vorkommen
  • alle Zeilen auswählen, in denen max_syllables größer oder gleich 9 ist
  • alle Zeilen auswählen, in denen die Komplexität zwischen 2 und 3 liegt

Neue Spalten hinzufügen

Manchmal will man einer bestehenden Tabelle weitere Spalten anhängen. Diese Vektoren müssen natürlich dieselbe Länge haben wie die anderen Spalten der Tabelle.

Die einfachste Möglichkeit sieht m.E. so aus:

complexity$length_chars <- str_length(complexity$sentence) # Satzlänge in Zeichen
complexity

Außerdem gibt es die Funktion mutate(), mit der man auch gleich mehrere Spalten auf einmal anfügen und Berechnungen mit den bestehenden Spalten anstellen kann:

complexity |>
  select(sentence) |>
  mutate(length_chars = str_length(sentence),
         length_chars_centered = length_chars - mean(length_chars))

Spalten löschen

Um Spalten zu löschen, gibt es mehrere Möglichkeiten.

Einfach ist es, einfach alle Spalten außer der zu entfernenden auszuwählen:

complexity |>
  select(-id)

Das Ergebnis muss man dann natürlich wieder einem Objekt zuweisen.

Alternativ kann man auch einfach eine ganze Spalte auf NULL setzen, dann verschwindet sie aus dem ursprünglichen Objekt:

complexity$length_chars <- NULL
complexity
complexity$length_chars <- str_length(complexity$sentence) # wieder anfügen

Tabellen sortieren

Will man sich die Zeilen einer Tabelle in anderer Reihenfolge anzeigen lassen, kann man die Funktion arrange() verwenden:

complexity |>
  arrange(desc(mean_complexity))

desc() gibt an, dass die Spalte absteigend sortiert werden soll.

Man kann zusätzlich auch nach weiteren Spalten sortieren:

complexity |>
  arrange(fin_verbs, tokens)
complexity |>
  arrange(desc(max_syllables), desc(fin_verbs), desc(tokens))

Sollte eine Spalte fehlende Werte (NA) enthalten, werden diese stets ans Ende gesetzt, egal, ob man auf- oder absteigend sortiert.

Lang- und Breitformat (long/wide)

In der Datenwildnis bekommt man es früher oder später mit unterschiedlichen Präsentationsformen von Tabellen zu tun:

  • Im “Breitformat” steht jede Zeile für eine Beobachtungseinheit/statistische Einheit (z.B. Person, Land, Text oder Wort). Da es keine Redundanz gibt, sind Tabellen in diesem Format für Menschen einfach zu verstehen und zu bearbeiten. Im Falle wiederholter Messungen (z.B.: selbe Variable zu verschiedenen Zeitpunkten oder unter verschiedenen Bedingungen) gibt es dafür separate Spalten.
  • Im “Langformat” gibt es dagegen mehrere Zeilen für dieselbe Beobachtungseinheit, nämlich eine für jeden Zeitpunkt oder jede Bedingung. Alle Messungen derselben statistischen Variable befinden sich dann in derselben Spalte, während eine weitere Spalte die Kategorie (Zeit, Bedingung, …) angibt.

Da beide Formate gebräuchlich sind und viele Funktionen in R ein bestimmtes Eingabeformat erwarten (überwiegend das Langformat), sollte man wissen, wie man Tabellen von einem ins andere umwandeln kann. Im Tidyverse gibt es dafür die beiden Funktionen pivot_longer() und pivot_wider().

Zur Veranschaulichung nutzen wir Häufigkeitsdaten ausgewählter Substantive im geschriebenen und gesprochenen Teil des British National Corpus (BNC; siehe ?BNCcomparison):

BNCcomparison |> as_tibble()

Da die Spalten written und spoken beide Häufigkeiten enthalten, könnten wir diese Werte in eine einzige Spalte packen. Eine weitere müsste dann bloß die Modalität (geschrieben/gesprochen) angeben.

Um vom Breit- ins Langformat zu wechseln, benutzen wir pivot_longer():

BNC_long <- BNCcomparison |>
  pivot_longer(cols = written:spoken,
               names_to = "modality",
               values_to = "frequency")
BNC_long

Für die umgekehrte Richtung entsprechend pivot_wider():

BNC_long |>
  pivot_wider(names_from = "modality",
              values_from = "frequency")

In einer Vignette gibt es noch jede Menge weiterer Beispiele dazu:

vignette("pivot")

Daten beschreiben

Extrem mächtig sind in Kombination die beiden Funktionen group_by() zur Aufspaltung eines Datensatzes in Gruppen und summarise() zur Anwendung beliebiger Operationen auf diese Gruppen (Summenbildung, Berechnung von Mittelwerten oder Standardabweichungen, …):

complexity |>
  group_by(fin_verbs) |> 
  summarise(
    Anzahl = n(),
    Mittlere_Satzlaenge = mean(tokens),
    Mittlere_Komplexitaet = mean(mean_complexity)
  )

Deskriptive Statistiken

Simulierte Daten mit Häufigkeit des Alkoholkonsums diverser Personen, ihrem Alter, Geschlecht und den erreichten Punkten in einem Wissenstest:

punkte <- read_csv2("../data/punkte.csv")
## ℹ Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
## Rows: 100 Columns: 4
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ";"
## chr (1): Geschlecht
## dbl (3): Alter, Alkoholkonsum, Punkte
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Mittelwert, Median, Spannweite, Interquartilsabstand und Standardabweichung der Punkte:

mean(punkte$Punkte)
## [1] 49.92
median(punkte$Punkte)
## [1] 50
range(punkte$Punkte)
## [1] 19 91
IQR(punkte$Punkte)
## [1] 37
sd(punkte$Punkte)
## [1] 20.21834

describe() aus dem psych-Paket:

describe(punkte)

describeBy() aus demselben Paket:

describeBy(punkte, punkte$Geschlecht)
## 
##  Descriptive statistics by group 
## group: männlich
##               vars  n  mean    sd median trimmed   mad min max range  skew
## Alter            1 50 17.74  3.75     18   17.90  4.45  10  24    14 -0.32
## Geschlecht*      2 50  1.00  0.00      1    1.00  0.00   1   1     0   NaN
## Alkoholkonsum    3 50  4.32  2.61      5    4.40  1.48   0   9     9 -0.44
## Punkte           4 50 58.58 17.50     63   60.23 16.31  19  85    66 -0.70
##               kurtosis   se
## Alter            -0.72 0.53
## Geschlecht*        NaN 0.00
## Alkoholkonsum    -0.97 0.37
## Punkte           -0.64 2.47
## ------------------------------------------------------------ 
## group: weiblich
##               vars  n  mean    sd median trimmed   mad min max range skew
## Alter            1 50 13.86  3.99     13   13.32  2.97   9  24    15 1.03
## Geschlecht*      2 50  1.00  0.00      1    1.00  0.00   1   1     0  NaN
## Alkoholkonsum    3 50  1.82  2.41      0    1.43  0.00   0   8     8 1.03
## Punkte           4 50 41.26 19.15     35   39.10 17.79  20  91    71 0.79
##               kurtosis   se
## Alter             0.32 0.56
## Geschlecht*        NaN 0.00
## Alkoholkonsum    -0.24 0.34
## Punkte           -0.57 2.71

Manuelle Überblicksstatistiken

punkte |>
  group_by(Geschlecht) |>
  summarise(
    Alter_mean = mean(Alter),
    Alter_sd = sd(Alter),
    Punkte_mean = mean(Punkte),
    Punkte_sd = sd(Punkte),
    Alkoholkonsum_mean = mean(Alkoholkonsum),
    Alkoholkonsum_sd = sd(Alkoholkonsum)
  )

skim() aus dem skimr-Paket:

skim(punkte)
Data summary
Name punkte
Number of rows 100
Number of columns 4
_______________________
Column type frequency:
character 1
numeric 3
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
Geschlecht 0 1 8 8 0 2 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
Alter 0 1 15.80 4.32 9 12.0 16 19.0 24 ▇▆▆▅▃
Alkoholkonsum 0 1 3.07 2.80 0 0.0 3 6.0 9 ▇▂▅▃▁
Punkte 0 1 49.92 20.22 19 31.5 50 68.5 91 ▇▅▅▇▂

Auch nach Gruppen möglich:

punkte |>
  group_by(Geschlecht) |>
  skim()
Data summary
Name group_by(punkte, Geschlec…
Number of rows 100
Number of columns 4
_______________________
Column type frequency:
numeric 3
________________________
Group variables Geschlecht

Variable type: numeric

skim_variable Geschlecht n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
Alter männlich 0 1 17.74 3.75 10 15.25 18 20.75 24 ▃▃▇▇▅
Alter weiblich 0 1 13.86 3.99 9 11.00 13 16.00 24 ▇▅▃▁▂
Alkoholkonsum männlich 0 1 4.32 2.61 0 2.25 5 6.00 9 ▅▂▆▇▂
Alkoholkonsum weiblich 0 1 1.82 2.41 0 0.00 0 3.00 8 ▇▂▁▂▁
Punkte männlich 0 1 58.58 17.50 19 48.50 63 72.75 85 ▃▂▃▇▆
Punkte weiblich 0 1 41.26 19.15 20 25.00 35 53.75 91 ▇▃▁▂▁

Fehlende Daten

Für fehlende Werte gibt es in R den speziellen Wert NA:

NA
## [1] NA
zahlen <- c(13, 38.2, 8, NA, 98.21, NA, 8.15); zahlen
## [1] 13.00 38.20  8.00    NA 98.21    NA  8.15
c("a", NA, "z", "e")
## [1] "a" NA  "z" "e"

Mehr Informationen:

?"NA"

Will man mit Vektoren rechnen, die NA-Werte enthalten, ist das Ergebnis in der Regel NA:

mean(zahlen)
## [1] NA
sd(zahlen)
## [1] NA

Viele Funktionen erlauben aber den optionalen Parameter na.rm, den man auf TRUE setzen kann, um fehlende Werte zu ignorieren:

mean(zahlen, na.rm = TRUE)
## [1] 33.112
sd(zahlen, na.rm = TRUE)
## [1] 38.47676

Daneben gibt es (u.a.) auch die Funktion na.omit(), die alle fehlenden Werte aus einem Vektor oder alle Zeilen(!) aus einer Tabelle, in denen fehlende Werte vorkommen, entfernt.

Die Anwendung auf Tabellen will also gut überlegt sein – u.U. wirft man damit verwertbare Daten weg!

zahlen <- na.omit(zahlen)
mean(zahlen)
## [1] 33.112
test <- tibble(
  Geschlecht = c("männlich", "weiblich", NA, "weiblich") |> factor(),
  Punkte = c(10, 12, 11, 9)
)
test
test |>
  summarise(mean = mean(Punkte))
test |>
  na.omit() |>
  summarise(mean = mean(Punkte))

Daneben gibt es noch die Tidyverse-Funktion drop_na(), mit der alle Zeilen entfernt werden, die in den angegebenen Spalten fehlende Werte enthalten:

test |>
  drop_na() # wie na.omit(): alle Spalten berücksichtigt
test |>
  drop_na(Geschlecht) # -> Zeile 3 wird entfernt
test |>
  drop_na(Punkte) # -> Zeile 3 wird nicht entfernt

Weitere nützliche Funktionen aus dem Tidyverse sind z.B. replace_na() (um in bestimmten Spalten z.B. alle NA-Werte auf 0 zu setzen) oder fill().

Daten graphisch darstellen

Eine gute Einführung in die Visualisierung von Daten mit ggplot2/Tidyverse gibt es unter: http://r4ds.had.co.nz/data-visualisation.html.

ggplot2 setzt eine geschichtete “Graphikgrammatik” um (grammar of graphics). Auf den ersten Blick wirkt das womöglich verwirrend, weil sich die Syntax etwas von Standard-R unterscheidet. Eine statistische Graphik besteht in diesem Modell aus der Zuordnung (mapping) von Variablen des Datensatzes (data) zu ästhetischen Attributen (aes) geometrischer Objekte (geom).

  • data: Datensatz, der die Variablen enthält, die graphisch dargestellt werden sollen
  • geom: Art geometrischer Objekte in der Graphik, z.B. Linien, Punkte oder Säulen
  • aes: ästhetische Attribute der geometrischen Objekte, z.B. die Position im Koordinatensystem (x, y), Farbe oder Form. Diese Attribute werden Variablen des Datensatzes zugeordnet.

(Vergleiche https://moderndive.com/2-viz.html)

Säulendiagramme

Ein Säulendiagramm mit ggplot():

ggplot(data = complexity) + # Datensatz angeben
  geom_bar(mapping = aes(x = tokens)) + # Welche Darstellung, welche Variable wohin?
  labs(title = "Satzlängenverteilung", # Titel über der Graphik
       x = "Satzlänge", # Beschriftung der x-Achse
       y = "Häufigkeit") # Beschriftung der y-Achse

complexity |> # sehr üblich: Datensatz per Pipe an ggplot() übergeben -- so kann man auch noch Funktionen zwischenschalten
  mutate(fin_verbs = factor(fin_verbs)) |> # fin_verbs für die Graphik zu Faktor machen
  ggplot(mapping = aes(x = tokens, fill = fin_verbs)) + # mapping hier gilt für alle folgenden Funktionen
  geom_bar() +
  labs(title = "Satzlängenverteilung",
       x = "Satzlänge",
       y = "Häufigkeit",
       fill = "Finite Verben")

Um ggf. die Anordnung der Säulen zu ändern, müssen wir wissen, wie ggplot sie standardmäßig sortiert: - wenn das Objekt eine Zahl ist, nach Größe - wenn das Objekt ein Faktor ist, nach Reihenfolge der Ausprägungen - wenn das Objekt ein Vektor aus Zeichenketten (characters) ist, nach Alphabet

Liegen die Häufigkeiten direkt vor (wie in unserer oben erstellten Tabelle decow), lässt sich geom_col() verwenden. Man muss dann lediglich auch angeben, aus welcher Spalte die Werte auf der y-Achse kommen sollen:

decow |>
  mutate(Token = fct_inorder(Token)) |> # damit die Reihenfolge der Säulen stimmt
  ggplot(mapping = aes(x = Token, y = Freq_pMT)) +
  geom_col() +
  labs(title = "Die häufigsten Tokens im DECOW14",
       y = "Häufigkeit pro Million Tokens")

Um das Erscheinungsbild zu ändern, lassen sich auch vordefinierte Themes verwenden (und ggf. noch weiter anpassen). Eine Übersicht gibt es hier: https://ggplot2.tidyverse.org/reference/ggtheme.html Weitere Themes lassen sich über separate Pakete installieren, z.B.:

decow |>
  mutate(Token = fct_inorder(Token)) |>
  ggplot(mapping = aes(x = Token, y = Freq_pMT)) +
  geom_col() +
  labs(title = "Die häufigsten Tokens im DECOW14",
       y = "Häufigkeit pro Million Tokens") +
  theme_bw()

Wenn man am Ende coord_flip() anfügt, kann man aus einem Säulendiagramm übrigens ganz einfach ein Balkendiagramm machen.

Histogramme

complexity |> 
  ggplot(aes(mean_word_length)) +
  geom_histogram() +
  labs(title = "Verteilung mittlerer Wortlängen über die Sätze",
       x = "Mittlere Wortlänge in Zeichen",
       y = "Häufigkeit")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Die Gestalt des Histogramms hängt sehr von der Anzahl der Säulen (bins) ab – beim Vergleich unterschiedlicher Histogramme muss man also etwas aufpassen.

Man kann die Balkenzahl selbst festlegen oder eine bestimmte Methode zur Berechnung verwenden:

complexity |> 
  ggplot(aes(mean_word_length)) +
  geom_histogram(bins = 10) +
  labs(title = "Verteilung mittlerer Wortlängen über die Sätze",
       x = "Mittlere Wortlänge in Zeichen",
       y = "Häufigkeit")

Boxplots & Violin-Plots

Ideal für Gruppenvergleiche

punkte |>
  ggplot(aes(x = Geschlecht,
             y = Alter)) +
  geom_boxplot() +
  labs(title = "Stichprobenzusammensetzung")

punkte |>
  ggplot(aes(x = Geschlecht,
             y = Alter)) +
  geom_violin() +
  labs(title = "Stichprobenzusammensetzung")

Streudiagramme

complexity |>
  ggplot(aes(x = tokens, y = mean_complexity)) +
  geom_point(alpha = .6) +
  scale_x_continuous(trans = "log10") +
  geom_smooth() +
  geom_smooth(method = "lm", colour = "red") +
  labs(title = "Zusammenhang zwischen Satzlänge und Komplexität",
       x = "Satzlänge (logarithmiert)",
       y = "Komplexität")
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'
## `geom_smooth()` using formula 'y ~ x'

Man kann in einen solchen Plot natürlich auch noch mehr Informationen packen:

complexity |>
  ggplot(aes(x = tokens,
             y = mean_complexity,
             size = max_dependency_distance,
             colour = fin_verbs)) +
  geom_point(alpha = .6) +
  scale_x_continuous(trans = "log10") +
  labs(title = "Zusammenhang zwischen Satzlänge und Komplexität",
       x = "Satzlänge (logarithmiert)",
       y = "Komplexität",
       colour = "Finite Verben",
       size = "Max. Dependenzabstand")

Korrelationsplots

complexity |>
  select(mean_word_length:mean_band_subtlex, length_chars, mean_complexity) |>
  cor() |>
  ggcorrplot(type = "lower",
             outline.color = "white",
             lab = TRUE)

Weitere Graphiken

Eine sehr schöne Liste von Graphiken, die sich mit ggplot erzeugen lassen (samt Code) gibt es hier:
http://r-statistics.co/Top50-Ggplot2-Visualizations-MasterList-R-Code.html